package image2

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"image/jpeg"
	"image/png"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"sync"
)

type Handler interface {
	Process(canvas *Canvas) error
}

type Image struct {
	canvas        *Canvas
	handlers      []Handler
	afterHandlers []Handler // 存放写字的handler
	Font          *truetype.Font
	wg            sync.WaitGroup
	errs          chan error
}

func NewCanvasImage(canvas *Canvas) *Image {
	im := new(Image)
	im.canvas = canvas
	return im
}

// 设置字体文件
func (i *Image) ReplaceFont(path string) {
	var (
		fontBytes []byte
		err       error
		font      *truetype.Font
	)

	fontBytes, err = ioutil.ReadFile(path)
	if err != nil {
		panic(fmt.Sprintf("字体文件未找到 -> %s", err.Error()))
	}
	font, err = freetype.ParseFont(fontBytes)
	if err != nil {
		panic(fmt.Sprintf("字体文件加载失败 -> %s", err.Error()))
	}
	i.Font = font
}

// 验证字体文件
func (i *Image) checkFontFile() bool {
	if i.Font != nil {
		return true
	}
	if len(i.canvas.FontFile) == 0 {
		return false
	}
	i.ReplaceFont(i.canvas.FontFile)
	return true
}

// 这个玩意的 x,y 坐标是以左下为原点 所以在处理的时候重置一下 传入的时候按左上传入 实际处理的纵坐标时候将行高加上
// 字体的居中问题
// 横向居中使用 paddingX 处理 (左右两侧都会生效，文字写入的宽度会在传入的宽度上减去两个 paddingX)
// 竖向居中使用 lineHeight 与 size 来控制
// 写入字符串
func (i *Image) WriteText(str string, x int, y int, paddingX int, size float64, lineHeight int, color string, w int, raw int, args *StringArgs) (line int, words []Word) {
	if !i.checkFontFile() {
		panic("请检查字体文件是否正确")
	}

	if w == 0 || raw == 0 {
		return
	}
	// 宽度w - 2*paddingX是实际上文字应占的宽度
	words = splitText(str, x+paddingX, y+lineHeight, size, lineHeight, w-2*paddingX, raw)
	for _, word := range words {
		if word.Word == "" {
			continue
		}
		line += 1
	}
	var text = &text{Words: words, Color: color, PaddingX: paddingX, Font: i.Font, Size: size, LineHeight: lineHeight}
	if args != nil {
		text.LineThrough = args.LineThrough
		text.TextBackground = args.TextBackground
	}
	// 如果写的文本需要背景色且未设置透明度或透明度比较小 1最大 255最小 那么就可以前置 否则后置
	if text.TextBackground != nil && (text.TextBackground.Opacity != 0 || text.TextBackground.Opacity > 200) {
		i.JoinHandlers(text)
	} else {
		i.afterHandlers = append(i.afterHandlers, text)
	}
	return
}

func (i *Image) JoinHandlers(handlers ...Handler) {
	i.handlers = append(i.handlers, handlers...)
}

// 直接返回到接口资源
func (i *Image) WriteToResource(w http.ResponseWriter) error {
	if err := i.processHandlers(); err != nil {
		return err
	}
	w.Header().Set("Content-Type", "image2/png")
	return png.Encode(w, i.canvas.Canvas)
}

// 返回生成的文件地址
func (i *Image) WriteToFile(fileName string) (filePath string, err error) {
	if err = i.processHandlers(); err != nil {
		return "", err
	}

	filePath = "/tmp/" + fileName
	fileWrite, err := os.Create(filePath)
	if err != nil {
		return filePath, errors.New("create image2 failed. ")
	}

	ext := path.Ext(fileName)
	switch ext {
	case ".png":
		err = png.Encode(fileWrite, i.canvas.Canvas)
	case ".jpg":
		err = jpeg.Encode(fileWrite, i.canvas.Canvas, &jpeg.Options{Quality: 100})
	default:
		err = errors.New("不支持的图片格式")
	}
	if err != nil {
		return "", errors.New("create image failed. " + err.Error())
	}

	return
}

// 返回base64
func (i *Image) WriteToBase64() (b string, err error) {
	var (
		buf bytes.Buffer
	)

	if err = i.processHandlers(); err != nil {
		return b, err
	}

	err = png.Encode(&buf, i.canvas.Canvas)
	if err != nil {
		return b, errors.New("生成失败" + err.Error())
	}

	return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}

// 返回bytes
func (i *Image) WriteToBytes() ([]byte, error) {
	var (
		buf bytes.Buffer
	)

	if err := i.processHandlers(); err != nil {
		return nil, err
	}

	if err := png.Encode(&buf, i.canvas.Canvas); err != nil {
		return nil, errors.New("生成失败" + err.Error())
	}

	return buf.Bytes(), nil
}

func (i *Image) processHandlers() error {
	// 先载入画布
	if err := i.canvas.GenerateDrawCanvas(); err != nil {
		return err
	}

	var maxIndex = len(i.handlers) - 1
	i.errs = make(chan error, maxIndex+1)
	defer close(i.errs)
	// 反着写
	for index := range i.handlers {
		i.wg.Add(1)
		go i.throughHandler(i.handlers[maxIndex-index])
	}
	i.wg.Wait()
	if len(i.errs) > 0 {
		return <-i.errs
	}

	_ = i.canvas.FullEmptyPixel()
	// 后置写文字 文字区域会被重新绘画
	for _, handler := range i.afterHandlers {
		if err := handler.Process(i.canvas); err != nil {
			return err
		}
	}
	return nil
}

func (i *Image) throughHandler(handler Handler) {
	defer i.wg.Done()
	if err := handler.Process(i.canvas); err != nil {
		i.errs <- err
	}
}
